//
//  CTVHTTPMediaDownload.m
//  Channltv
//
//  Created by Bruno Keymolen on 29/08/07.
//  Copyright 2007 channl.tv. All rights reserved.
//

#import "CTVHTTPMediaDownload.h"
#import "CTVConfig.h"
#import "CTVCache.h"
#import "CTVUtil.h"
#import "CTVSystemFeedback.h"

@implementation CTVHTTPMediaDownload

-(id)initWithURLString:(NSString*)urlString andClip:(CTVClip*)clip andMediaReply:(CTVMediaDownloadReply*)mediaReply{
///	[super init];
	[super initWithClip:clip andMediaReply:mediaReply];
	
	//m_downloadUpdates = [[NSMutableArray alloc] init];
//	m_clip = clip;
//	[m_clip retain];
	
//	m_mediaReply = mediaReply;
	m_urlString = urlString;
	[m_urlString retain];

	m_intervalTimeLeftTimerInit = 5.0f; //start update "Time Left" calls
	m_intervalTimeLeftTimer = 2.0f; // "Time Left" timer
	
	m_fileHandle = nil;
	m_downloadedLength = 0;
	m_downloadFileLength = -1;

	m_connection = nil;

	m_tempFile = [[NSString alloc] initWithFormat:@"%@_%@", [[CTVConfig GetInstance] mediaCacheDir] , [CTVCache getHashString:m_urlString] ];
	
	return self;
}


-(void)dealloc{

	if(m_timer){
		[m_timer invalidate];
		m_timer = nil;
	}	

//	[m_downloadUpdates release];
	[m_urlString release];
	[m_tempFile release];
//	[m_clip release];

	if(m_startTime){
		[m_startTime release];
	}
	
	if(m_connection){
		[m_connection release];
	}
	if(m_fileHandle > 0){
		fclose(m_fileHandle);
		m_fileHandle = 0;
	}
	
	[super dealloc];
}



//Return: 0:download started, 1=already in cache, <1:error
-(int)startDownload{
	
	NSString* cacheUrl = [CTVCache getMediaCacheEntryForURL:m_urlString];
	if(cacheUrl != nil){
		return 1;
	}

	//Create temporary file
	if(m_fileHandle <= 0){
		m_fileHandle = fopen( [m_tempFile UTF8String] ,"w");
	}
	if(m_fileHandle <= 0){
		return -1;
	}
		

	//Download
	NSURL* url = [NSURL URLWithString:m_urlString];

	NSMutableURLRequest* urlRequest=[NSMutableURLRequest requestWithURL:url
						cachePolicy:NSURLRequestReloadIgnoringCacheData
						timeoutInterval:15.0];

	//Modify some Headers
	[urlRequest setValue:[[CTVConfig GetInstance] userAgent] forHTTPHeaderField:@"User-Agent"];

	//Get the data
	if(m_connection){
		[m_connection cancel];
		[m_connection release];
	}
	m_connection=[[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
	
	if (m_connection == nil) {
		[CTVSystemFeedback systemClipFeedBackWithMessage:@"Error; Download Clip; Unable to setup the connection to given URL" andClipID:[m_clip clipID] ];	
		return -1;
	}	
	
	//Start the timer to do the "time left" updates every 3', start only after 7'
	m_timer = [NSTimer scheduledTimerWithTimeInterval:m_intervalTimeLeftTimerInit target:self selector:@selector(reportTimeLeft:) userInfo:nil repeats:YES];

	//The download is busy
	
	return 0;
}


-(int)stopDownload{
	[m_connection cancel];


	[m_mediaReply finishMediaDownload:self]; //Tell our object creator the download is done	
	[self reportDownloadError:CTVDownloadErrorAbort]; //Inform other components about this
	[m_mediaReply releaseMediaDownload:self]; //Ask our object creator to release the object from memory	

	return 0;
}



-(NSString*)urlString{
	return m_urlString;
}

-(long)downloadFileLength{
	return m_downloadFileLength;
}

-(long)downloadedLength{
	return m_downloadedLength;
}


//-(void)addDownloadUpdate:(CTVMediaDownloadUpdate*)update{
//	if( [m_downloadUpdates containsObject:update] == FALSE){
//		[m_downloadUpdates addObject:update];
//	}
//}
//
//-(void)removeDownloadUpdate:(CTVMediaDownloadUpdate*)update{
//	[m_downloadUpdates removeObject:update];
//}

-(CTVClip*)clip{
	return m_clip;
}


/*
 *
 * NSURLConnection Delegates
 *
 */
- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
}


- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
}


//Calls to CTVMediaDownloadUpdate(rs)
-(void)reportDownloadError:(int)err{
	NSEnumerator *enumerator = [m_downloadUpdates objectEnumerator];
	CTVMediaDownloadUpdate* update;
	while (update = [enumerator nextObject]) {
		[update downloadError:err];
	}
	//Stop the timer
	[m_timer invalidate];
	m_timer = nil;
}
-(void)reportDownloadPercent:(int)pc{
	NSEnumerator *enumerator = [m_downloadUpdates objectEnumerator];
	CTVMediaDownloadUpdate* update;
	while (update = [enumerator nextObject]) {
		[update downloadPercent:pc];
	}
}
-(void)reportDownloadFinish{
	NSEnumerator *enumerator = [m_downloadUpdates objectEnumerator];
	CTVMediaDownloadUpdate* update;
	while (update = [enumerator nextObject]) {
		[update downloadFinish];
	}
	//Stop the timer
	[m_timer invalidate];
	m_timer = nil;
}
///

/* FAIL */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
	NSLog(@"Error; File download: %@", m_urlString);

	if(m_httpStatusCode == 0){
		m_httpStatusCode = -900;
	}

	NSString* sysString = [NSString stringWithFormat:@"rm %@", m_tempFile];
	NSLog(sysString);	
	system([sysString UTF8String]);
	
	[m_mediaReply finishMediaDownload:self]; //Tell our object creator the download is done		
	[self reportDownloadError:CTVDownloadErrorConnection];	

	//Tell the channl.tv server about the error
	[CTVSystemFeedback systemClipFeedBackWithMessage:@"Error; Download Clip; Connection Failed" andClipID:[m_clip clipID]];

	[m_mediaReply releaseMediaDownload:self]; //Ask our object creator to release the download
}


/* read data */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
	m_downloadedLength	= m_downloadedLength + [data length];
	
	//Write data to the temporary file 	
	int ret = fwrite([data bytes], 1, [data length], m_fileHandle);

	if(m_downloadFileLength > 0){
		float pc = ((float)m_downloadedLength / (float)m_downloadFileLength) * 100.0f;						
		[self reportDownloadPercent:(int)pc];
	}

	
	if(ret != [data length]){
		NSLog(@"ERROR: CTVHTTPDownload dropped %d bytes; continue...", ([data length] - ret) );
	}
}



// Full header is available
// In rare cases, for example in the case of an HTTP load where the content type of the load data is 
// multipart/x-mixed-replace, the delegate will receive more than one connection:didReceiveResponse: message. 
// In the event this occurs, delegates should discard all data previously delivered by 
// connection:didReceiveData:, and should be prepared to handle the, potentially different,
// MIME type reported by the newly reported URL response.
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
	NSHTTPURLResponse* urlResponse = (NSHTTPURLResponse*)response;

	NSLog(@"Full header is received");
	
	m_httpStatusCode = [urlResponse statusCode];
	if([urlResponse statusCode] >= 400){
		//Could not download resource
		[m_connection cancel];

		[m_mediaReply finishMediaDownload:self]; //Tell our object creator the download is done		
		[self reportDownloadError:CTVDownloadErrorConnection];	
		
		//Feedback to channl.tv server
		NSString* msg = [NSString stringWithFormat:@"Error; Download Clip; HTTP Status Code:%d", m_httpStatusCode];
		[CTVSystemFeedback systemClipFeedBackWithMessage:msg andClipID:[m_clip clipID]];

		[m_mediaReply releaseMediaDownload:self]; //Ask our object creator to release the download
	}


	//Remember our start time
	m_startTime = [NSDate date];
	[m_startTime retain];
	
	//Analyse the Headers so we know the file size etc.
	NSDictionary* dict = [urlResponse allHeaderFields];
	NSString* contentRange = [dict objectForKey:@"Content-Range"];
	if(contentRange != nil){
		NSRange range;
		int len;
		[CTVUtil parseHTTPHeaderContentRange:contentRange resultRange:&range resultSize:&len];
		
		m_downloadFileLength  = len;
	}else{
		NSString* contentLength = [dict objectForKey:@"Content-Length"];
	
		if(contentLength != nil){
			int len;
			[CTVUtil parseHTTPHeaderContentLength:contentLength resultSize:&len];
			m_downloadFileLength  = len; 
		}else{
			m_downloadFileLength = -1; //Unknown
		}
	}	
}


-(NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse{
	//Return Value
	//The actual cached response to store in the cache. The delegate may return cachedResponse unmodified, 
	//return a modified cached response, or return nil if no cached response should be stored for the 
	//connection.
	return nil;
}


-(NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse{
	NSLog(@"Redirect while downloading %@", m_urlString);
	
	//Oops, we have a redirect, modify the headers to our needs
	NSMutableURLRequest* urlRequest= [request mutableCopyWithZone:nil];	
		
	//Modify some Headers
	[urlRequest setValue:[[CTVConfig GetInstance] userAgent] forHTTPHeaderField:@"User-Agent"];	
	
	return urlRequest;
}


-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
	NSString* ext = @"avi";
	
	int diff = m_downloadFileLength - m_downloadedLength;
	if(diff < 0) diff = -diff;
	if(m_downloadedLength < 5 * 1024 || /* at least 5k must be downloaded */
	   (m_downloadFileLength > 0 && (diff > 1 * 1024))  ){ /* a difference of 1K is allowed */
		
		NSString* msg = [NSString stringWithFormat:@"Error; Download Clip; Incorrect File Size (header:%d bytes, downloaded:%d bytes)", m_downloadFileLength, m_downloadedLength];
		[CTVSystemFeedback systemClipFeedBackWithMessage:msg andClipID:[m_clip clipID]];

		NSLog(@"Download Error, incorrect file size....");
		NSString* sysString = [NSString stringWithFormat:@"rm %@", m_tempFile];
		NSLog(sysString);	
		system([sysString UTF8String]);	
		
		[m_mediaReply finishMediaDownload:self]; //Tell our object creator the download is done		
		[self reportDownloadError:CTVDownloadErrorFileSize];
		[m_mediaReply releaseMediaDownload:self]; //Ask the object creator to release the download		
		return;
	}
	
	
	if(m_fileHandle > 0){
		fclose(m_fileHandle);
		m_fileHandle = 0;
	}
	
	if(m_fileHandle <= 0){
		m_fileHandle = fopen([m_tempFile UTF8String] , "r");
	}
	
	if(m_fileHandle > 0){
		fseek(m_fileHandle, 0, SEEK_SET);
		int buflen = 250;
		char buf[buflen];
		//Read the firsts 100 bytes
		int ret = fread(buf, 1, buflen, m_fileHandle);
		NSData* data = [NSData dataWithBytes:buf length:ret];
		NSString* extret = [CTVUtil findFileTypeExtensionForUrlString:m_urlString andHeaderData:data];
		if(extret != nil){
			ext = extret;
		}
	}
	
	if(m_fileHandle > 0){
		fclose(m_fileHandle);
		m_fileHandle = 0;
	}

	//Rename temporary file to full download
	NSString* m_chacheEntry =  [NSString stringWithFormat:@"%@%@.%@", [[CTVConfig GetInstance] mediaCacheDir] , [CTVCache getHashString:m_urlString], ext ];
	
	NSString* sysString = [NSString stringWithFormat:@"mv %@ %@", m_tempFile, m_chacheEntry];
NSLog(sysString);	
	system([sysString UTF8String]);
	

	//We are done
	[m_mediaReply finishMediaDownload:self]; //Tell our object creator the download is done		
	[self reportDownloadFinish];
	[m_mediaReply releaseMediaDownload:self]; //Ask our object creator to release the download
}


-(NSString*)cacheEntryNoExtension{
	NSString* cacheEntry =  [NSString stringWithFormat:@"%@%@", [[CTVConfig GetInstance] mediaCacheDir] , [CTVCache getHashString:m_urlString]];
	return cacheEntry;
}


-(int)downloadRestTimeSec{
	int ret = -1;
		
	if(m_startTime != nil){
		NSTimeInterval ti = [m_startTime timeIntervalSinceNow];
		ti = -ti; //makes it a bit easyer to read
		if(ti > 0 && m_downloadedLength > 0){
			ret = ((ti/m_downloadedLength) * m_downloadFileLength) - ti;
		} 
	}
	
	return ret;
}


-(void)reportTimeLeft:(NSTimer*)timer{
	if([m_downloadUpdates count] > 0){	 
		int tl = [self downloadRestTimeSec];	
		if(tl>0){
			NSEnumerator *enumerator = [m_downloadUpdates objectEnumerator];
			CTVMediaDownloadUpdate* update;
			while (update = [enumerator nextObject]) {
				[update downloadTimeLeft:tl];
			}
		}
	}

	//If no data is received for 45 sec, we break the connection ourselves (if the IP stack did not already did it :)
	

	
	//If we are still in init (long delay) start a faster update now
	if([timer timeInterval] == m_intervalTimeLeftTimerInit){
		[m_timer invalidate];
		m_timer = [NSTimer scheduledTimerWithTimeInterval:m_intervalTimeLeftTimer target:self selector:@selector(reportTimeLeft:) userInfo:nil repeats:YES];
	}
	
}


-(int)httpStatusCode{
	return m_httpStatusCode;
}


@end

